﻿#include "ssRedisConnection.h"
#ifdef _WIN32
#include <WinSock2.h>
#endif

namespace StarSeeker
{

std::string ssRedisConnection::s_ok("OK");

ssRedisConnection::ssRedisConnection()
{
	m_conn = 0;
	m_host = "127.0.0.1";
	m_port = 6379;
	m_errno = 0;
	m_free = true;
}

ssRedisConnection::ssRedisConnection(const char *hostname, int port)
{
	m_conn = 0;
	m_host = hostname;
	m_port = port;
	m_errno = 0;
	m_free = true;
}

ssRedisConnection::~ssRedisConnection()
{
	Disconnect();
}

void ssRedisConnection::Init(const char *hostname, int port)
{
	m_host = hostname;
	m_port = port;
}

int ssRedisConnection::Connect()
{
	if (m_conn)
		return 0;

	m_errno = 0;
	timeval timeout = { 1, 0 }; // 1.0 seconds
	m_conn = redisConnectWithTimeout(m_host.c_str(), m_port, timeout);
	if (m_conn == NULL || m_conn->err) {
		if (m_conn) {
			m_errno = m_conn->err;
			m_error = "Connection error: ";
			m_error += m_conn->errstr;
			redisFree(m_conn);
		}
		else {
			m_errno = -1;
			m_error = "Connection error: can't allocate redis context";
		}
		m_conn = 0;
	}
	return m_errno;
}

void ssRedisConnection::Disconnect()
{
	if (m_conn)
	{
		redisFree(m_conn);
		m_conn = 0;
	}
}

int ssRedisConnection::Ping()
{
	static std::string _pang("PONG");
	m_errno = 0;
	Connect();
	if (m_conn)
	{
		redisReply* reply = (redisReply*)redisCommand(m_conn, "PING");
		if (reply)
		{
			if (_pang==reply->str)//成功
			{
				freeReplyObject(reply);
				return 0;
			}
			else
			{
				freeReplyObject(reply);
				Disconnect();
			}
		}
		else
		{
			Disconnect();
		}
		Connect();
	}

	return m_errno;
}

void ssRedisConnection::FreeReply(redisReply * pReply)
{
	freeReplyObject(pReply);
}

redisReply* ssRedisConnection::Command(const char *str)
{
	m_errno = 0;
	if (Ping()==0)
	{
		redisReply* reply = (redisReply*)redisCommand(m_conn, str);
		if (reply)
		{
			return reply;
		}
		else
		{
			m_errno = -1;
			m_error = str;
			m_error += ": connection error";
			Disconnect();
		}
	}
	return 0;
}

redisReply* ssRedisConnection::VCommand(const char *format, ...)
{
	m_errno = 0;
	if (Ping() == 0)
	{
		va_list ap;
		va_start(ap, format);
		redisReply *reply = (redisReply*)redisvCommand(m_conn, format, ap);
		va_end(ap);
		if (reply)
		{
			return reply;
		}
		else
		{
			m_errno = -1;
			m_error = format;
			m_error += ": connection error";
			Disconnect();
		}
	}
	return 0;
}

int ssRedisConnection::Execute(const char *str)
{
	if (Ping() == 0)
	{
		redisReply* reply = (redisReply*)redisCommand(m_conn, str);
		if (reply)
		{
			m_errno = 0;
			freeReplyObject(reply);
		}
		else
		{
			m_errno = -1;
			m_error = str;
			m_error += ": connection error";
			Disconnect();
		}
	}
	return m_errno;
}

int ssRedisConnection::VExecute(const char *format, ...)
{
	if (Ping() == 0)
	{
		va_list ap;
		va_start(ap, format);
		redisReply *reply = (redisReply*)redisvCommand(m_conn, format, ap);
		va_end(ap);
		if (reply)
		{
			m_errno = 0;
			freeReplyObject(reply);
		}
		else
		{
			m_errno = -1;
			m_error = format;
			m_error += ": connection error";
			Disconnect();
		}
	}
	return m_errno;
}

int ssRedisConnection::TTL(const char * key)
{
	int ret = 0;
	m_cmd = "TTL ";
	m_cmd += key;
	redisReply* reply = Command(m_cmd.c_str());
	if (CheckReply(reply, REDIS_REPLY_INTEGER, false))
	{
		ret = reply->integer;
	}
	freeReplyObject(reply);
	return ret;
}

int ssRedisConnection::Del(const char * key)
{
	m_cmd = "DEL ";
	m_cmd += key;
	redisReply* reply = Command(m_cmd.c_str());
	CheckReply(reply, REDIS_REPLY_INTEGER, false);
	freeReplyObject(reply);
	return m_errno;
}

bool ssRedisConnection::Exists(const char * key)
{
	bool ret = false;
	m_cmd = "EXISTS ";
	m_cmd += key;
	redisReply* reply = Command(m_cmd.c_str());
	CheckReply(reply, REDIS_REPLY_INTEGER, true);
	freeReplyObject(reply);
	return m_errno==0;
}

int ssRedisConnection::Expire(const char * key, int seconds)
{
	SS_REDIS_FORMAT_BEGIN
	m_stream << "EXPIRE " << key << ' '<< seconds;
	SS_REDIS_FORMAT_END
	redisReply* reply = Command(m_cmd.c_str());
	CheckReply(reply, REDIS_REPLY_INTEGER, false);
	freeReplyObject(reply);
	return m_errno;
}

int ssRedisConnection::Get(const char * key, std::string& val)
{
	m_cmd = "GET ";
	m_cmd += key;
	redisReply* reply = Command(m_cmd.c_str());
	if (CheckReply(reply, REDIS_REPLY_STRING, false))
	{
		val = reply->str;
	}
	freeReplyObject(reply);
	return m_errno;
}

bool ssRedisConnection::Hexists(const char *key, const char *field)
{
	m_cmd = "HEXISTS ";
	m_cmd += key;
	m_cmd += ' ';
	m_cmd += field;
	redisReply* reply = Command(m_cmd.c_str());
	CheckReply(reply, REDIS_REPLY_INTEGER, false);
	freeReplyObject(reply);
	return m_errno;
}

int ssRedisConnection::Hget(const char * key, const char * field, std::string & val)
{
	m_cmd = "HGET  ";
	m_cmd += key;
	m_cmd += ' ';
	m_cmd += field;
	redisReply* reply = Command(m_cmd.c_str());
	if (CheckReply(reply, REDIS_REPLY_STRING, false))
	{
		val = reply->str;
	}
	freeReplyObject(reply);
	return m_errno;
}

int ssRedisConnection::HMget(std::list<std::pair<int, std::string> >& outList, const char * key, std::initializer_list<const char*>&& keyList)
{
	m_cmd = "HMGET ";
	m_cmd += key;
	for (auto& it: keyList)
	{
		m_cmd += " ";
		m_cmd += it;
	}
	redisReply* reply = Command(m_cmd.c_str());
	if (CheckReply(reply, REDIS_REPLY_ARRAY, false))
	{
		std::pair<int, std::string> temp;
		for (size_t i = 0; i < reply->elements; i++)
		{
			temp.second.clear();
			temp.first = reply->element[i]->type;
			if (reply->element[i]->str)
				temp.second = reply->element[i]->str;
			outList.push_back(temp);
		}
	}
	freeReplyObject(reply);
	return 0;
}

int ssRedisConnection::HMget(std::vector<std::pair<int, std::string> >& outList, const char * key, std::vector<std::string>& keyList)
{
	m_cmd = "HMGET ";
	m_cmd += key;
	for (auto& it : keyList)
	{
		m_cmd += " ";
		m_cmd += it;
	}
	redisReply* reply = Command(m_cmd.c_str());
	if (CheckReply(reply, REDIS_REPLY_ARRAY, false))
	{
		std::pair<int, std::string> temp;
		for (size_t i = 0; i < reply->elements; i++)
		{
			temp.second.clear();
			temp.first = reply->element[i]->type;
			if (reply->element[i]->str)
				temp.second = reply->element[i]->str;
			outList.push_back(temp);
		}
	}
	freeReplyObject(reply);
	return 0;
}

int ssRedisConnection::HgetAll(std::list<std::pair<std::string, std::string>>& outList, const char * key)
{
	m_cmd = "HGETALL ";
	m_cmd += key;
	redisReply* reply = Command(m_cmd.c_str());
	if (CheckReply(reply, REDIS_REPLY_ARRAY, false))
	{
		std::pair<std::string, std::string> p;
		for (size_t i = 0; i < reply->elements; i++)
		{
			if (i%2==0)
			{
				p.first = reply->element[i]->str;
			} 
			else
			{
				p.second = reply->element[i]->str;
				outList.push_back(p);
			}
		}
	}
	freeReplyObject(reply);
	return 0;
}

void ssRedisConnection::ClearStream()
{
	m_stream.str("");
	m_stream.clear();
	m_cmd.clear();
}

bool ssRedisConnection::CheckReply(redisReply * pReply, int type, bool checkValue)
{
	if (pReply)
	{
		if (pReply->type == type)
		{
			m_errno = 0;
			if (checkValue)
			{
				switch (type)
				{
				case REDIS_REPLY_INTEGER:
					if (pReply->integer > 0)
						return true;
					break;
				case REDIS_REPLY_STRING:
				case REDIS_REPLY_STATUS:
					if (pReply->str == ssRedisConnection::s_ok)
						return true;
					break;
				case REDIS_REPLY_ARRAY:
					return true;
				default:
					break;
				}
			} 
			else
			{
				return true;
			}
		} 
		m_errno = REDIS_ERR_OTHER;
		m_error = m_cmd + ": return error";
	}
	return false;
}

}
